package com.loren.designer.utils;

import com.loren.designer.exception.SystemException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class BeanUtils {

    /**
     * Object -> T
     */
    public static <T> T sourceToTarget(Object source, Class<T> target, String... ignoreProperties) {
        try {
            T instance = target.newInstance();
            org.springframework.beans.BeanUtils.copyProperties(source, instance, ignoreProperties);
            return instance;
        } catch (IllegalAccessException | InstantiationException e) {
            log.error("an error occurred while converting data", e);
            throw new SystemException("an error occurred while converting data");
        }
    }

    /**
     * List<Object> -> List<T>
     */
    public static <T> List<T> sourceListToTargetList(List<?> sourceList, Class<T> target, String... ignoreProperties) {
        return sourceList.stream().map(source -> sourceToTarget(source, target, ignoreProperties)).collect(Collectors.toList());
    }

    /**
     * Map<String, Object> -> T
     */
    public static <T> T sourceMapToTarget(Map<String, Object> sourceMap, Class<T> target) {
        try {
            T instance = target.newInstance();
            org.apache.commons.beanutils.BeanUtils.populate(instance, underlineMapConvertCamelMap(sourceMap));
            return instance;
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
            log.error("an error occurred while converting data", e);
            throw new SystemException("an error occurred while converting data");
        }
    }

    /**
     * List<Map<String, Object>> -> List<T>
     */
    public static <T> List<T> sourceMapListToTargetList(List<Map<String, Object>> sourceMapList, Class<T> target) {
        return sourceMapList.stream().map(sourceMap -> sourceMapToTarget(sourceMap, target)).collect(Collectors.toList());
    }

    /**
     * Object -> T
     */
    public static <T> T deepSourceToTarget(Object source, Class<T> target) {
        return JSONObject.parseObject(JSONObject.toJSONString(source), target);
    }

    /**
     * List<Object> -> List<T>
     */
    public static <T> List<T> deepSourceListToTargetList(List<?> sourceList, Class<T> target) {
        return sourceList.stream().map(o -> deepSourceToTarget(o, target)).collect(Collectors.toList());
    }

    /**
     * Object -> T [Underline -> Camel]
     */
    public static <T> T deepUnderlineSourceToTarget(Object source, Class<T> target) {
        String jsonString = JSONObject.toJSONString(source);
        Map<String, Object> map = JSONObject.parseObject(jsonString, new TypeReference<Map<String, Object>>() {
        });
        return deepSourceToTarget(underlineMapConvertCamelMap(map), target);
    }

    /**
     * List<Object> -> List<T> [Underline -> Camel]
     */
    public static <T> List<T> deepUnderlineSourceListToTargetList(List<?> sourceList, Class<T> target) {
        return sourceList.stream().map(source -> deepSourceToTarget(source, target)).collect(Collectors.toList());
    }

    private static Map<String, Object> underlineMapConvertCamelMap(Map<String, Object> underlineMap) {
        Map<String, Object> camelMap = new LinkedHashMap<>();
        for (Map.Entry<String, Object> entry : underlineMap.entrySet()) {
            camelMap.put(underlineToCamel(entry.getKey()), entry.getValue());
        }
        return camelMap;
    }

    private static String underlineToCamel(String underline) {
        String regexp = "_";
        if (underline == null || "".equals(underline.trim()) || !underline.contains(regexp)) {
            return underline;
        }
        String[] words = underline.split(regexp);
        StringBuilder camel = new StringBuilder(words[0]);
        for (int i = 1, length = words.length; i < length; i++) {
            char c = words[i].charAt(0);
            camel.append((c >= 'a') && (c <= 'z') ? (char) (c & 0x5f) : c).append(words[i].substring(1));
        }
        return camel.toString();
    }
}
